package com.zh.auth.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.zh.auth.domain.dto.LoginDto;
import com.zh.auth.domain.dto.RegisterDto;
import com.zh.auth.mapper.UserRoleMapper;
import com.zh.auth.service.AuthService;
import com.zh.auth.service.RoleService;
import com.zh.common.constant.AuthConstant;
import com.zh.common.dto.IpDto;
import com.zh.common.dto.UserAuthDto;
import com.zh.common.exception.AuthException;
import com.zh.common.exception.BusinessException;
import com.zh.common.properties.JwtProperties;
import com.zh.common.utils.RSAUtil;
import com.zh.common.utils.RedisUtil;
import com.zh.common.utils.SHAUtil;
import com.zh.web.base.ApiResult;
import com.zh.web.domain.auth.dto.UserDto;
import com.zh.web.domain.auth.entity.User;
import com.zh.web.domain.auth.entity.UserRole;
import com.zh.web.domain.auth.vo.RoleVo;
import com.zh.web.domain.auth.vo.UserVo;
import com.zh.web.enums.Status;
import com.zh.web.openApi.UserClient;
import com.zh.web.utils.JwtUtil;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
@Slf4j
/*
  认证服务
 */
public class AuthServiceImpl implements AuthService {

    private final RedisUtil redisUtil;
    private final RoleService roleService;
    private final JwtUtil jwtUtil;
    private final JwtProperties jwtProperties;
    private final UserRoleMapper userRoleMapper;
    private final UserClient userClient;
    private static final String DEFAULT_AVATAR = "http://8.137.38.55:9000/lawsweep/avatar/default_avatar.png";
    // 创建一个带有过期时间的缓存，过期时间为5分钟
    private final Cache<String, String> decryptedPasswordsCache = Caffeine.newBuilder()
            .expireAfterWrite(5, TimeUnit.MINUTES) // 设置过期时间
            .maximumSize(1000) // 设置缓存的最大容量
            .build();

    /**
     * 获取公钥
     *
     * @return 公钥
     */
    @Override
    public String getPublicKey() {
        try {
            if (CollUtil.isEmpty(RSAUtil.keyPairMap)) {
                RSAUtil.generateKey();
            }
        } catch (Exception e) {
            throw new BusinessException("生成公钥失败");
        }
        return RSAUtil.getPublicKey();
    }

    @Override
    public String getPrivateKey() {
        try {
            if (CollUtil.isEmpty(RSAUtil.keyPairMap)) {
                RSAUtil.generateKey();
            }
        } catch (Exception e) {
            throw new BusinessException("生成私钥失败");
        }
        return RSAUtil.getPrivateKey();
    }

    /**
     * 登录，是否已登录过了
     *
     * @param request  请求
     * @param loginDto 登录参数
     * @return Boolean
     */
    @Override
    public UserVo login(HttpServletRequest request, HttpServletResponse response, LoginDto loginDto, IpDto ipDto) {
        //1.先检查请求头是否存在token 存在先删除旧的登录信息：实现一台设备一个用户
        String token = request.getHeader(jwtProperties.getTokenName());
        if (token != null) {
            jwtUtil.invalidateToken(token);
        }
        return this.login(loginDto, response, ipDto);
    }

    /**
     * 首次登录
     *
     * @param loginDto 登录参数
     * @param response 响应 token存入header
     * @return Boolean
     */
    @Override
    public UserVo login(LoginDto loginDto, HttpServletResponse response, IpDto ipDto) {
        //1. 获取原始密码字符串
        String originPassword = getDecryptedPassword(loginDto.getPassword());
        //2. 查询用户信息
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, loginDto.getUsername());
        UserDto userDto = BeanUtil.copyProperties(loginDto, UserDto.class);
        User user = userClient.getUser(userDto).getData();
        if (user == null) {
            throw new AuthException("不存在该账号");
        }
        if (Status.BANED.equals(user.getStatus())) {
            throw new AuthException("该账号已被封禁! 请联系管理员");
        }
        //3.校验用户信息
        if (ObjectUtil.isNull(user) || !SHAUtil.getSHA256(originPassword, user.getSalt()).equals(user.getPassword())) {
            throw new AuthException("用户名或密码错误");
        }
        if (Status.DISABLE.equals(user.getStatus())) {
            throw new AuthException("账户已禁用");
        }
        //4.校验通过，完成登录记录并返回token
        UserAuthDto userAuthDto = new UserAuthDto();
        userAuthDto.setId(user.getId());
        userAuthDto.setRememberMe(loginDto.getRememberMe());
        userAuthDto.setIpDto(ipDto);
        String signToken = jwtUtil.signToken(userAuthDto);
        response.setHeader(jwtProperties.getTokenName(), signToken);
        UserVo userVo = BeanUtil.copyProperties(user, UserVo.class);
        List<RoleVo> roleVoList = roleService.getRoleByUserId(user.getId());
        userVo.setIpInfo(ipDto);
        userVo.setRoleList(roleVoList);
        return userVo;
    }

    /**
     * 需要seata保证事务
     *
     * @param registerDto 注册参数
     * @return Boolean
     */
    @Override
    @GlobalTransactional
    public String register(RegisterDto registerDto) {
        String decryptedPassword = getDecryptedPassword(registerDto.getPassword());
        String decryptedConfirmPassword = getDecryptedPassword(registerDto.getConfirmPassword());
        if (!decryptedPassword.equals(decryptedConfirmPassword)) {
            throw new AuthException("两次密码不一致");
        }
        User user = new User();
        String salt = SHAUtil.generateSalt();
        user.setUsername(registerDto.getUsername());
        user.setPassword(SHAUtil.getSHA256(decryptedPassword, salt));
        user.setSalt(salt);
        user.setNickname(registerDto.getNickname());
        user.setAvatar(DEFAULT_AVATAR);
        if (ObjectUtil.isEmpty(registerDto.getNickname())) {
            Integer count = userClient.getUserCount().getData();
            count++;
            user.setNickname("法王_" + count);
        }
        try {
            Long userId = userClient.addUser(user).getData();
            if (userId == null) {
                throw new BusinessException("新增用户失败");
            }
            UserRole userRole = new UserRole();
            userRole.setUserId(userId);
            userRole.setRoleCode(AuthConstant.USER_CODE);
            userRoleMapper.insert(userRole);
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
        return "注册成功!";
    }

    @Override
    public String refreshToken(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(jwtProperties.getTokenName());
        if (token != null) {
            String refreshedToken = jwtUtil.refreshToken(token);
            response.setHeader(jwtProperties.getTokenName(), refreshedToken);
            return "刷新token成功!";
        }
        return "刷新token失败!";
    }

    /**
     * 根据token获取用户登录信息
     *
     * @param authToken token
     * @return 用户ID
     */
    @Override
    public Map<String, Object> getUserSessionByToken(String authToken) {
        String jwt = jwtUtil.getJwt(authToken);
        String key = jwtProperties.getRedisKeyPrefix() + jwt;
        return redisUtil.get(key);
    }

    /**
     * 踢出登录 token失效
     */
    @Override
    public String invalidateToken(String authToken) {
        jwtUtil.invalidateToken(authToken);
        return "退出登录成功";
    }

    @Override
    public Boolean checkToken(String authToken) {
        return jwtUtil.chickToken(authToken);
    }

    /**
     * 缓存解密后的密码
     *
     * @param encryptedPassword 密文
     * @return 解密后的密码
     */
    private String getDecryptedPassword(String encryptedPassword) {
        return decryptedPasswordsCache.get(encryptedPassword, key -> {
            try {
                return RSAUtil.decryptByPrivateKey(key, RSAUtil.getPrivateKey());
            } catch (Exception e) {
                log.error("解密失败", e);
                return null;
            }
        });
    }
}
